package joc.internal.transformer;

import java.util.List;

import javassist.CtClass;
import javassist.CtConstructor;
import javassist.CtMethod;
import javassist.Modifier;
import joc.CallSequenceProvider;
import joc.JOCCallSequence;
import joc.internal.EvaluationResult;
import joc.internal.Evaluator;
import joc.internal.MethodCall;
import joc.internal.SingletonConditionCache;
import joc.internal.compiler.CastExp;
import joc.internal.compiler.ConstructorExp;
import joc.internal.compiler.NestedExp;
import joc.internal.compiler.StandaloneExp;
import joc.internal.compiler.StaticCallExp;
import joc.internal.compiler.ValueExp;

public class MethodCallSequenceTransformer extends ClassTransformer {

	public MethodCallSequenceTransformer(Transformer transformer) {
		super(transformer);
	}

	@Override
	public void transform(CtClass clazz) throws Exception {
		List<CtClass> consultedTypes = getConsultedTypes(clazz);
		consultedTypes.add(clazz);
		for (CtClass consultedType : consultedTypes) {
			getLogger().debug("consulted type: " + consultedType.getName());
			if (consultedType.hasAnnotation(JOCCallSequence.class)) {
				addCallSequence(clazz, consultedType);
			}
		}
	}

	public void addCallSequence(CtClass clazz, CtClass callSequenceClass) throws Exception {

		getLogger().info("adding callsequence from " + callSequenceClass.getName() + " to " + clazz.getName());
		if (Modifier.isFinal(clazz.getModifiers())) {
			clazz.setModifiers(Modifier.clear(clazz.getModifiers(), Modifier.FINAL));
		}
		JOCCallSequence annotation = (JOCCallSequence) callSequenceClass.getAnnotation(JOCCallSequence.class);
		ValueExp cacheStringId = new ValueExp(callSequenceClass.getName());
		NestedExp cacheObjId = NestedExp.THIS;
		NestedExp getProviderCall = new CastExp(CallSequenceProvider.class, new StaticCallExp(
				SingletonConditionCache.getCondition, cacheObjId, cacheStringId));

		StandaloneExp saveConditionCall = new StaticCallExp(SingletonConditionCache.saveConditionIfNotExists,
				cacheObjId, cacheStringId, new ConstructorExp(annotation.value())).toStandalone();
		for (CtConstructor constructor : clazz.getConstructors()) {
			getLogger().info(
					"callsequence code after constructor " + constructor.getLongName() + ": " + saveConditionCall);
			saveConditionCall.insertAfter(constructor);
		}
		for (CtMethod method : clazz.getDeclaredMethods()) {
			NestedExp verificationCall = getProviderCall.appendCall(CallSequenceProvider.verifyCall,
					new ConstructorExp(MethodCall.class, new ValueExp(method.getName())));
			StandaloneExp evaluatorCall = new StaticCallExp(Evaluator.evaluateContract, new ConstructorExp(
					EvaluationResult.class, new ValueExp(getRecipientString(method)), new ValueExp(
							JOCCallSequence.class), verificationCall, new ValueExp(
							"Invalid method call in call sequence."))).toStandalone();
			getLogger().info("callsequence code before method " + method.getLongName() + ": " + evaluatorCall);
			evaluatorCall.insertBefore(method);
		}
	}
}
